#include "particle.h"

#define ITER_LIST( iter,_list ) \
   for(iter=_list->begin(); iter!=_list->end(); ++iter)

void Particle::addConstraint ( Constraint & _constraint ) {    //#D
   if (!_constraint.lasting()) 
      volatileConstraints->push_back( &_constraint );
   else
      staticConstraints->push_back( &_constraint );

}

void Particle::honorConstraints() {    //#D
   Constraint* constraint;
   while( ! volatileConstraints->empty() ) {
      constraint = volatileConstraints->front();
      volatileConstraints->pop_front();
      (*constraint).honor( this );
   }

   std::list<Constraint*>::iterator sConstraint;
   ITER_LIST (sConstraint, staticConstraints ) {
      (*sConstraint)->honor( this );
   }
}

const Circle* Particle::getCircle() const {
   circle->setXY ( 
	 drawable->getPosRect()->x+ pd->circle->x,
	 drawable->getPosRect()->y+ pd->circle->y
	 );
   return circle; 
}

void ParticleList::add ( Particle * particle ) {      //#D
   List::add ( (Listable*) particle );
}

Particle * ParticleList::iter () {     //#D
   return ( Particle* ) List::listIter ();
}

void ParticleEngine::add ( Particle & particle) {     //#D
   particles.push_back ( &particle );
   drawableList->add ( particle.getDrawable() );
}

void ParticleEngine::applyFields () {
   std::list<Particle*>::iterator particle;
   std::list<Field>::iterator field;
   for(particle=particles.begin(); particle != particles.end(); ++particle) {
      (*particle)->resetAcc();
      for(field=fields.begin(); field != fields.end(); ++field) field->fap( (*particle), pc );
   }
}

void ParticleEngine::actualize ( ) {	  //#D
   std::list<Particle*>::iterator particle;
   UINT ticks = pc->timer->getTicks();
   applyFields();
   checkCollisions();
   for(particle=particles.begin(); particle != particles.end(); ++particle) {
      (*particle)->honorConstraints();

      (*particle)->vel += ( (*particle)->acc * pc->timeResulution );
      (*particle)->pos += ( (*particle)->vel * pc->timeResulution );

      (*particle)->actualizeSprite( ticks );
   }
   removeParticles();
}

void ParticleEngine::addField ( Field & _field ) {    //#D
   fields.push_back ( _field );
}

void ParticleEngine::checkCollisions() {
   std::list<Particle*>::iterator a;
   std::list<Particle*>::iterator b;

   for ( a=particles.begin(); a!=particles.end(); ++a) {
      for ( b=a; b!=particles.end(); ++b) {
	 checkCollision ( (*a), (*b) );
      }
   }
}

void ParticleEngine::checkCollision( Particle* a, Particle* b) {
   CollisionData cd(0,0);
   CollisionData* pcd = &cd;
   std::list<collisionCheckfp>::iterator collisionCheck;

   if ( checkCircleCollision(
		  *( a->getCircle() ), 
		  *( b->getCircle() ), &pcd ) ) {
      collisionCheck = collisionCheckfps.begin();
      while ( collisionCheck != collisionCheckfps.end() ) { 
	 if ((*collisionCheck)(a,b,&pcd)) break;
	 ++collisionCheck;
      }
      
   }
}

bool null_pointer( const void* value ) { return (value==NULL); }

void ParticleEngine::removeParticles () {    //#D
   std::list<Particle*>::iterator particle;
   
   for(particle=particles.begin(); particle != particles.end(); ++particle) {
      if ( ( (*particle)->pos.getx() > pc->width ) || 
	    ( (*particle)->pos.getx() < ( pc->start->getx() - pc->offset ) ) ) {
	 delete (*particle);
	 (*particle)=NULL;
	 pDebug("List Size is now: " << particles.size() - 1 );
      }
   }

   particles.remove_if(null_pointer);
}


void ParticleEngine::addCollisionCheck ( collisionCheckfp _ccfp) {
   collisionCheckfps.push_back( _ccfp );
}

#define MAX(a,b) ( (a>b)?a:b )

//returns 1 if collision 0 otherwise
int checkRectCollision( Rect& a, Rect &b) {
   if (&a==&b) return 0;

   int left1, left2;
   int right1, right2;
   int top1, top2;
   int bottom1, bottom2;
   
   left1 = a.x;
   left2 = b.x;
   right1 = a.x + a.w;
   right2 = b.x + b.w;
   top1 = a.y;
   top2 = b.y;
   bottom1 = a.y + a.h;
   bottom2 = b.y + b.h;
   
   if (bottom1 < top2) return(0);
   if (top1 > bottom2) return(0);
   
   if (right1 < left2) return(0);
   if (left1 > right2) return(0);
   
   return(1);
}

bool checkCircleCollision ( const Circle &a, const Circle &b, CollisionData** cd ) {
   if ( &a==&b ) return false;

   float dist = SQR(a.x-b.x) + SQR(a.y-b.y) ;
   float diameter = SQR(a.radius+b.radius);

   /*pDebug("Collision check " << dist << " " << diameter );
   pDebug("Circle a.r " << a.radius << " Circle b.r " << b.radius )
   pDebug("Cixcle a.x " << a.x<< " Cixcle b.x " << b.x)
   pDebug("Ciycle a.y " << a.y<< " Ciycle b.y " << b.y) */
   
   (*cd)->set(dist,diameter);
   if ( dist < diameter ) return true;
   return false;
}

Constraint::Constraint() {
   longLasting = false;
}

PlaneConstraint::PlaneConstraint ( Vector const & _normal, Vector const & _position ) : Constraint()  {
   normal = _normal.versor();
   position = _position;
}

void PlaneConstraint::honor ( Particle *p ) {
   // I'll code the general case at once if the particle has speed in the
   // direction of the normal towards the plane then such speed will become null
   // if the particle is sticky or reversed otherwise
   
   //get the component of the acc in the direction of the normal

   float accProjection = p->acc*normal;
   float velProjection = p->vel*normal;

   Vector acc = normal*accProjection;
   if (accProjection<0.)
      //we substract that acc from the particle
      p->acc -= acc;

   //Now the velocity
   Vector vel = normal*velProjection;
   if (velProjection<0.) {
      if ( p->pd->properties & PARTICLE_IS_UNELASTIC ) {
	 //the velocity in the direction will become cero
	 p->vel-=vel;
      } else {
	 p->vel-=vel*2.;
      }
   }
}

bool solidCollision ( Particle* a, Particle* b, CollisionData** cd) {
   if ( ( a->pd->properties & PARTICLE_IS_SOLID ) && ( b->pd->properties & PARTICLE_IS_SOLID ) ) {
      Vector v_a = a->vel-b->vel;
      Vector normal = (a->pos - b->pos).versor();
      Vector perp = normal.perpVersor();
      
      float var = (v_a*perp);
      float val = (v_a*normal);
      float ma = a->pd->mass;
      float mb = b->pd->mass;
      float ual = val*(-1.)*(mb-ma)/(mb+ma);
      float ubl = val*(2.*ma)/(mb+ma);
      v_a = normal*ual+perp*var;
      a->vel = b->vel + v_a;
      b->vel += normal*ubl;
      
      (*cd)->sqrtDists();
      float diff = (*cd)->diameter - (*cd)->dist;
      if (diff>0) {
	 a->pos += normal*(diff/2.);
	 b->pos -= normal*(diff/2.);
      }
      return true;
   }
   return false;
}
